package mongohelper.impl;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import mongohelper.MongoTranslator;
import mongohelper._MongoObjectTranslator;
import mongohelper.annotations.MongoField;
import mongohelper.annotations.MongoObject;
import mongohelper.exceptions.MongoTranslationException;
import util.classpath.ClasspathWorker;

import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;

public class MongoDbObjectTranslator implements MongoTranslator {

	private static void configureMaps(final Map<Field, MongoField> mongoFieldMap, Class<?> clazz) {
		do {
			for (Field f : clazz.getDeclaredFields()) {
				MongoField ann = f.getAnnotation(MongoField.class);
				if (ann != null)
					mongoFieldMap.put(f, ann);
			}

			clazz = clazz.getSuperclass();

		} while (!clazz.equals(Object.class));

	}

	private final Map<Class<?>, _MongoObjectTranslator<?>> translatorMap = new HashMap<Class<?>, _MongoObjectTranslator<?>>();

	public MongoDbObjectTranslator(final String packageToScan) throws Exception {
		for (Class<?> clazz : ClasspathWorker.getClassesInPackage(packageToScan)) {
			processClass(clazz);
		}
	}

	private void processClass(final Class<?> clazz) {
		final MongoObject objAnnotation = clazz.getAnnotation(MongoObject.class);
		if (objAnnotation != null) {
			translatorMap.put(clazz, generateMongoObjectTranslator(clazz, objAnnotation));
		}
	}

	/**
	 * Generate a {@link _MongoObjectTranslator} that will convert back and
	 * forth between POJO and {@link DBObject}. TODO - conversion and encryption
	 * 
	 * @param type
	 *            The type of POJO to translate
	 * @param objAnnotation
	 *            The {@link MongoObject} annotation on the class
	 * @return The {@link _MongoObjectTranslator} implementation generated by
	 *         reflectively analyzing the class provided
	 */
	public <T> _MongoObjectTranslator<T> generateMongoObjectTranslator(final Class<T> type, final MongoObject objAnnotation) {

		final Map<Field, MongoField> mongoFieldMap = new HashMap<Field, MongoField>();
		configureMaps(mongoFieldMap, type);

		return new _MongoObjectTranslator<T>() { // _MongoObjectTranslator<T>

			@Override
			public T fromDb(DBObject obj) throws MongoTranslationException {
				T instance = null;
				try {
					instance = type.newInstance();
				} catch (InstantiationException | IllegalAccessException e) {
					throw new MongoTranslationException("Issue constructing new instance of type " + type.getName(), e);
				}

				for (final Field f : mongoFieldMap.keySet()) {
					f.setAccessible(true);
					final MongoField mf = mongoFieldMap.get(f);
					Object value = obj.get(mf.fieldName());
					if (value instanceof DBObject) {
						value = fromDbObject((DBObject) value, f.getType());
					}
					// TODO - Decrypt
					// TODO - Translate
					try {
						f.set(instance, value);
					} catch (IllegalArgumentException | IllegalAccessException e) {
						throw new MongoTranslationException("Issue setting field " + f.getName() + " in type " + type.getName(), e);
					}
				}
				return instance;
			}

			@Override
			public DBObject toDb(T instance) throws MongoTranslationException {
				final BasicDBObject dbObj = new BasicDBObject();
				for (final Field f : mongoFieldMap.keySet()) {
					MongoField mf = mongoFieldMap.get(f);
					f.setAccessible(true);
					try {
						Object obj = f.get(instance);
						// TODO - Translate
						// TODO - Encrypt
						if (obj != null && obj.getClass().getAnnotation(MongoObject.class) != null) {
							obj = toDbObject(obj);
						}
						dbObj.put(mf.fieldName(), obj);
					} catch (IllegalArgumentException | IllegalAccessException e) {
						throw new MongoTranslationException("Issue reflectively getting value from POJO to store in DBObject", e);
					}
				}
				return dbObj;
			}

		}; // End _MongoObjectTranslator<T>

	}

	@SuppressWarnings("unchecked")
	public <T> T fromDbObject(final DBObject obj, final Class<T> desiredType) throws MongoTranslationException {
		return (T) translatorMap.get(desiredType).fromDb(obj);
	}

	@SuppressWarnings("unchecked")
	public <T> DBObject toDbObject(final T instance) throws MongoTranslationException {
		return ((_MongoObjectTranslator<T>) translatorMap.get(instance.getClass())).toDb(instance);
	}

	/*
	 * Test
	 */

	public static void main(String[] args) throws Exception {
		new MongoDbObjectTranslator("mongohelper");
	}

}
